/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package motiveai.learn;

import motiveai.factors.Environment;
import motiveai.factors.EnvironmentSharedData;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import motiveai.components.Agent;
import motiveai.components.BehaviourEntity;
import motiveai.components.MotiveAI;

/**
 *
 * @author oxplay
 */
public class LearnCentralUnit {

    private final static Logger logger = Logger.getLogger(LearnCentralUnit.class.getName());
    private ArrayList<RegisteredBehaviourClass> registeredBehaviours = new ArrayList<RegisteredBehaviourClass>();
    private EnvironmentSharedData actualEnvironmentSharedData;
    private Environment actualEnvironment;
    private LearningType basedDecision;
    private AbstractLearningChooser customBasedDecision;
    private MotiveAI manager;
    private boolean simulation = false;

    public void setSimulation(boolean simulation) {
        this.simulation = simulation;
    }

    public boolean isSimulation() {
        return simulation;
    }
    
    public LearnCentralUnit(MotiveAI manager) {
        this.manager = manager;
        logger.setLevel(Level.OFF);
    }

    public long learnCountLeft() {
        int number = 0;
        for (Iterator<EnvironmentSharedData> it = manager.getEnvironmentDefinition().getEnvironmentSharedData().values().iterator(); it.hasNext();) {
            EnvironmentSharedData environmentSharedData = it.next();
            number += manager.getMaxCountLearn() - environmentSharedData.getMaxCountOfBehave();
        }
        return number;
    }

    public long learnCountLeft(Environment environment) {
        return manager.getMaxCountLearn() - environment.getEnvironmentSharedData().getMaxCountOfBehave();
    }

    public void setBasedDecision(LearningType basedDecision) {
        this.basedDecision = basedDecision;
    }

    public void setCustomBasedDecision(AbstractLearningChooser customBasedDecision) {
        this.customBasedDecision = customBasedDecision;

    }

    /*
     * Register Behaviour for letting know about able behave.
     */
    public void registerBehaviour(Class behaviour) {
        if (behaviour.getSuperclass().isAssignableFrom(BehaviourEntity.class)) {
            registeredBehaviours.add(new RegisteredBehaviourClass(behaviour.getSimpleName(), behaviour));
        } else {
            logger.log(Level.SEVERE, "MotiveAI registerBehaviour isAssignableFrom failure!");
        }
    }

    /*
     * Set Environment as actual for Learn Central Unit processor
     */
    public void setActualEnvironment(Environment actualEnvironment) {
        this.actualEnvironment = actualEnvironment;
        this.actualEnvironmentSharedData = actualEnvironment.getEnvironmentSharedData();
    }

    /*
     * Return list of all Behaviours in Environment, even not tested ones.
     */
    public ArrayList<BehaviourEntity> getBehaviourList(Agent agent) {
        ArrayList<BehaviourEntity> allBehaviourList = new ArrayList<BehaviourEntity>();
        Map<String, BehaviourEntity> behaviourMemory = this.actualEnvironment.getBehaviourMemory();
        Set<Class> unusedBehaviours = new HashSet<Class>();
        for (Iterator<RegisteredBehaviourClass> it = registeredBehaviours.iterator(); it.hasNext();) {
            RegisteredBehaviourClass registeredBehaviour = it.next();
            BehaviourEntity memoryBehaviour = behaviourMemory.get(registeredBehaviour.name);
            if(simulation || agent.canUseBehaviour(registeredBehaviour.behaviourClass.getSimpleName())){
                if (memoryBehaviour != null) {
                    if (memoryBehaviour.canBeUsed(agent, actualEnvironment)) {
                        allBehaviourList.add(memoryBehaviour);
                    }
                } else {
                    unusedBehaviours.add(registeredBehaviour.behaviourClass);
                }
            }
        }
        try {
            for (Iterator<Class> it = unusedBehaviours.iterator(); it.hasNext();) {
                Class behaviourClass = it.next();
                BehaviourEntity behaviour = (BehaviourEntity) behaviourClass.newInstance();
                if (behaviour.canBeUsed(agent, actualEnvironment)) {
                    allBehaviourList.add(behaviour);
                }
            }
        } catch (InstantiationException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
        return allBehaviourList;
    }

    /*
     * Return best performance Behaviour from memory.
     */
    public BehaviourEntity getBestBehaviour(Agent agent) {
        ArrayList<BehaviourEntity> behaviourMemory = getBehaviourList(agent);
        BehaviourEntity choosedBehaviour = null;
        if (customBasedDecision != null) {
            choosedBehaviour = customBasedDecision.choose(behaviourMemory, actualEnvironment);
        } else {
            float lastDecisionFactor = 0f;
            for (Iterator<BehaviourEntity> it = behaviourMemory.iterator(); it.hasNext();) {
                BehaviourEntity behaviour = it.next();
                if (behaviour.mustBeUsed(agent, actualEnvironment)) {
                    return behaviour;
                }
                float actualDecisionFactor = 0f;
                if (manager.getMaxCountLearn() <= actualEnvironmentSharedData.getMaxCountOfBehave()) {
                    // already learned - need to use best behaviour
                    actualDecisionFactor = behaviour.getPerformance();
                } else {
                    // learning procedures - need to choose behaviour
                    float countBased = ((actualEnvironmentSharedData.getMaxCountOfBehave() + 1) / (behaviour.getCountMemory() + 1));
                    if (basedDecision.equals(LearningType.STANDARD)) {
                        actualDecisionFactor = countBased * (float) behaviour.getPerformance();
                    } else if (basedDecision.equals(LearningType.HIGH_PRECISE)) {
                        actualDecisionFactor = countBased * (float) Math.sqrt(behaviour.getPerformance());
                    } else if (basedDecision.equals(LearningType.LOW_PRECISE)) {
                        actualDecisionFactor = countBased * (float) Math.pow(behaviour.getPerformance(), 2);
                    }
                }
                if (lastDecisionFactor < actualDecisionFactor) {
                    lastDecisionFactor = actualDecisionFactor;
                    choosedBehaviour = behaviour;
                }
            }
        }
        return choosedBehaviour;
    }

    /*
     * Use system output to print Environment Data.
     */
    public void printDebugInformation() {
        System.out.println("---LearnCentralUnit DebugInformation---");
        for (Iterator<EnvironmentSharedData> it = manager.getEnvironmentDefinition().getEnvironmentSharedData().values().iterator(); it.hasNext();) {
            EnvironmentSharedData env = it.next();
            System.out.println("Environment hash: " + env.getHashCode() + " performance: " + env.getPerformance() + " MaxCountOfBehave: " + env.getMaxCountOfBehave() + " learnCountLeft: " + env.getLearnCountLeft(manager));
            for (Iterator<String> it2 = env.getBehaviourMemory().keySet().iterator(); it2.hasNext();) {
                String name = it2.next();
                BehaviourEntity behaviour = env.getBehaviourMemory().get(name);
                System.out.println("Environment Behaviour " + name + " count: " + behaviour.getCountMemory() + " performance: " + behaviour.getPerformance());
            }
        }
    }
}
